
package org.midi.util;

/**
 * Manages shared resources, making sure that each resource is never used by
 * more than one client at any given time.
 */
public class ResourcePool {

	private final Resource[] pool;
	private boolean traceEnabled = false;

	/**
	 * Constructs a ResourcePool to manage the specified resources.
	 */
	// NOTE: byte[][] is less general, but Dave Morehouse reports that Object[] fails verification on some devices 
	public ResourcePool(byte[][] resources) {
		pool = new Resource[resources.length];
		for (int i = 0; i < resources.length; i++) {
			pool[i] = new Resource(resources[i]);
		}
	}

	/**
	 * Calls claimResource, catching InterruptedException.
	 */
	public Object claimResourceIgnoreInterrupt() {
		for (;;) {
			try {
				return claimResource();
			}
			catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Claims a resource, waiting until one is available if necessary. You MUST
	 * pass the returned resource to releaseResource when you're done with it 
	 * or it will never be made available again. Use try/finally to ensure that
	 * claimed resources are released.
	 */
	public synchronized Object claimResource() throws InterruptedException {
		 for (;;) {
			for (int i = 0; i < pool.length; i++) {
				Resource r = pool[i];
				if (!r.claimed) {
					r.claimed = true;
					if (traceEnabled) 
						r.trace = new Throwable();
					return r.resource;
				}
			}
			// No resources available.
			wait();
		}
	}

	/**
	 * Releases a previously claimed resource.
	 */
	public synchronized void releaseResource(Object resource) {
		for (int i = 0; i < pool.length; i++) {
			Resource r = pool[i];
			if (r.resource == resource) {
				if (!r.claimed) 
					throw new IllegalArgumentException("Tried to release an unclaimed resource: " + resource);
				r.claimed = false;
				r.trace = null;
				notify();
				return;
			}
		}
		throw new IllegalArgumentException("Not a known resource: " + resource);
	}

	/**
	 * Returns true if claimResource would return without blocking. Be sure to
	 * synchronize on the ResourcePool instance between calling 
	 * isResourceAvaialble and claimResource, or another thread might claim the
	 * resource between the two calls.
	 */
	public synchronized boolean isResourceAvailable() {
		for (int i = 0; i < pool.length; i++) {
			if (!pool[i].claimed) return true;
		}
		return false;
	}

	/**
	 * Dumps stack traces of when each claimed resource was claimed. This can 
	 * be used for debugging purposes, to locate the source of a resource leak.
	 * Only works if setTraceEnabled(true) was previously called.
	 */
	public synchronized void dumpTrace() {
		System.err.println("ResourcePool resource claimant trace:");
		for (int i = 0; i < pool.length; i++) {
			Resource r = pool[i];
			if (!r.claimed) 
				System.err.println("(unclaimed)");
			else if (r.trace == null) 
				System.err.println("(no trace available)");
			else r.trace.printStackTrace();
		}
	}

	/**
	 * Enables or disables resource claim tracing, as required for dumpTrace. 
	 * Be aware that enabling tracing may have performance implications.
	 */
	public synchronized void setTraceEnabled(boolean traceEnabled) {
		this.traceEnabled = traceEnabled;
	}


	private static final class Resource {
		final Object resource;
		boolean claimed = false;
		Throwable trace = null; // stack trace from when the resource was claimed

		Resource(Object r) {
			this.resource = r;
		}
	}

}
